home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 1.iso / ARGONET / PD / FILER / X-FILESRC.ZIP / xrecover / c / x-recover
Text File  |  1997-01-07  |  67KB  |  2,330 lines

  1. /* x-recover.c 0·09
  2.  * Based (partly) on x-check.c by Andy Armstrong
  3.  * Needs chunks.h from the X-files distribution
  4.  *
  5.  * © Nicholas Clark 1996/1997
  6.  *
  7.  * Version history:
  8.  *
  9.  * 0·01 28/12/96
  10.  * First version. Successfully extracts all chunks from an (intact) X-File.
  11.  * Not really tested - did get all of the Perl 5.003 source (640 files)
  12.  * Checkmap still happy afterwards 8-)
  13.  *
  14.  * 0·02 30/12/96
  15.  * Added options to specify file offset of chunktable and root directory
  16.  * (Get this from x-guess)
  17.  * Implemented chunktable. Root directory may prove more problematic.
  18.  * (Currently do entire directory in one go by malloc()ing a buffer and reading
  19.  * the lot in. I guess I'm going to have to hunt the root directory in the chunk
  20.  * table to find out how big it is (xFiles_dirHeader.size is the *number* of
  21.  * entries, rather than the (floating) size of the names))
  22.  *
  23.  * 0·03 30/12/96
  24.  * Re-wrote dir code to read directories file by file. This allows x-recover to
  25.  * 1: Cope with directories that have an incorrect size in the chunkTable
  26.  *    (I have one of these I made accidentally during testing - I copied an
  27.  *     x-File after the chunktable was written, but before the cached directory
  28.  *     was written)
  29.  * 2: Read directories (eg the root directory) from an arbitrary disc location.
  30.  *
  31.  * 0·04 01/01/97
  32.  * Method 2 fully implemented
  33.  * Recovers all files that correlate between directories and chunktable
  34.  * Attempts to match chunkless files to chunks of the same size.
  35.  * Recovers these files.
  36.  * Recovers all so-far unrecovered chunks
  37.  * [Methods 1 and 2 still rely on an intact chunktable. If your chunktable is
  38.  * missing (and x-guess can't find it) or destroyed, you have a problem.
  39.  * It's just like trying to recover a disc with no discmap - you have to guess
  40.  * file type and size from the contents (assuming that there is some
  41.  * characteristic to recognise.
  42.  * Until someone writes a centralised file type database containing:
  43.  *   mime type
  44.  *   dos extension
  45.  *   unix suffix
  46.  *   mac type
  47.  *   identification by contents (/etc/magic in Unix)
  48.  *   [optionally icon]
  49.  * xrecover_guess-o-matic will have to wait.]
  50.  *
  51.  * 0·05 02/01/97
  52.  * Spellchecked the source code!
  53.  * eg snytax is now syntax
  54.  * Important lesson: Check you executables before distribution
  55.  * It did compile, but it didn't work. Fixed it
  56.  * Much now tidier if xrecover_recoverChunkChkdsk fails to read any bytes
  57.  * Tidied up "no dirhash for filename entry" check
  58.  *
  59.  * 0·06 02/01/97
  60.  * Merged x-guess as option -g
  61.  * Added déjà vu flag to stop infinite loops if the directory tree becomes
  62.  * tangled (precursor to method 3 - readDirsRaw which will have to reparent
  63.  * directories)
  64.  * Fixed -r to figure out the chunk the root dir occupies (if possible)
  65.  * (Usually 100 !) Wrote the documentation. We do documentation?
  66.  *
  67.  * 0·07 03/01/97
  68.  * Added -a and -f flags. (and implemented them!)
  69.  *
  70.  * 0·08 04/01/97
  71.  * Documentation up to date.
  72.  * Can now do options all run together - eg
  73.  * x-recover  -nvv1t 1024 $.Yaffel.dump.Perl.perl5003
  74.  * (or x-recover  -nvv1t1024 $.Yaffel.dump.Perl.perl5003)
  75.  * although one must have a space after the offset of -r or -t
  76.  * Fixed two bugs ( déjà vu flag and sprintf to a possible NULL pointer )
  77.  * See below for our policy on bugs.
  78.  *
  79.  * 0·09 07/01/97
  80.  * Fixed design - now believe the get the number of chunks from chunktable chunk
  81.  * 0 rather than the header if a chunktable offset is specified
  82.  *
  83.  * Bugs: We don't do bugs.
  84.  *
  85.  * None known. Please report bugs (preferably with fixes) to <bagpuss@done.net>
  86.  * If you can supply an x-File to demonstrate then this would be useful.
  87.  * Currently I'm quite happy for relevant e-mail up to 1Mb, but if
  88.  * bagpuss.done.net is up then use anonymous ftp to upload problem files.
  89.  * (Files up to 100Mb acceptable by this method. No, I'm not confusing Kb with
  90.  * Mb. If you're on Janet then you should be able to shift it to me in 7
  91.  * minutes.)
  92.  */
  93.  
  94. #include <stdio.h>
  95. #include <stdlib.h>
  96. #include <string.h>
  97. #include <ctype.h>
  98. #include <stdarg.h>
  99. #include <limits.h>
  100. #include "chunks.h"
  101.  
  102. #ifdef __riscos
  103. #include "kernel.h"
  104. #include "SWIs.h"
  105. #endif
  106.  
  107. #ifdef __GNUC__
  108. #include "unistd.h"
  109. #endif
  110.  
  111. #define _max(x, y) ((x) > (y) ? (x) : (y))
  112. #define _min(x, y) ((x) < (y) ? (x) : (y))
  113. #define _four( N ) ((((N)-1)|3)+1)
  114.  
  115. #ifndef FREE
  116. #define FREE 0x45455246
  117. #endif
  118.  
  119. #ifdef __riscos
  120. static const char *xrecover_dirSep = ".";
  121. #else
  122. /* Should work Unix, Amiga, DOG (DOG internals don't care whether it's \ or /
  123.  * Not that I care about DOG. Well I do actually - I care that it is extant and
  124.  * still wasting people's time, and I work towards its extinction )
  125.  * "Dummies Guide to Memory Management" - Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaargh
  126.  * *Any* decent system just works. (usually because it comes with a flat memory
  127.  * model.)
  128.  */
  129. static const char *xrecover_dirSep = "/";
  130. #endif
  131.  
  132. enum {
  133.   xrecover_bufferSize = xFiles_ALLOCATIONUNIT * 64
  134. };
  135. /* If I do #define then I get text substitution everywhere (including functions)
  136.  * I believe that const int will allocate memory (so that the address can be
  137.  * taken)
  138.  */
  139.  
  140. typedef union {
  141.   xFiles_header        header;
  142.   xFiles_chunk        chunk;
  143.   xFiles_dirHeader    dirHeader;
  144.   char            padding[xFiles_ALLOCATIONUNIT];
  145. } xguess_block;
  146.  
  147.  
  148. typedef struct
  149. {
  150.   xFiles_dirEntry    dirEntry;
  151.   char            name[xFiles_MAXNAME+1];
  152.   /* Does xFiles_MAXNAME include the '\0' ? */
  153. } xrecover_dirEntryWithName;
  154.  
  155.  
  156. typedef enum {
  157.   xrecover_untouched    = 0x00,
  158.   xrecover_touched    = 0x01,
  159.   xrecover_foundChunk    = 0x02,
  160.   xrecover_sizeMatches    = 0x04,
  161.   xrecover_started    = 0x08,
  162.   /* An attempt to recover named file has been made. Stops list iterator
  163.    * attempting to reopen an illegal filename several times.
  164.    */
  165.   xrecover_readWhole    = 0x10,
  166.   xrecover_crossLinked    = 0x20,
  167.   xrecover_openFail    = 0x40,
  168.   xrecover_dejaVu    = 0x80,    /* Flag on entering a directory, to stop
  169.                  * infinite loops if directory tree has become
  170.                  * tangled */
  171.   xrecover_sorted    = 0x1F    /* Everything hunky-dory with this file */
  172. } xrecover_fileStatus;
  173.  
  174. typedef xrecover_fileStatus xrecover_chunkStatus;
  175.  
  176. typedef struct xrecover_filenameList_struct
  177. {
  178.   struct xrecover_filenameList_struct    *next;
  179.   xrecover_fileStatus            status;
  180.   unsigned                node;    /* only found in dirHash */
  181.   xFiles_dirEntry            dirEntry;
  182. } xrecover_filenameListEntry;
  183.  
  184. typedef struct
  185. {
  186.   xrecover_chunkStatus        status;
  187.   /* Currently only used for the déjà vu flag.
  188.    * If it's going to be used by anything else check that every fileList
  189.    * operation correctly updates it */
  190.   xrecover_filenameListEntry    *file;
  191.   unsigned long            references;
  192. /* Not entirely sure what xFiles_chunk::usage is for - maybe I should use that
  193.  * to record how many directory entries I find pointing to the chunk */
  194. } xrecover_chunkTableCrossCheck;
  195.  
  196.  
  197.  
  198. typedef union {
  199.   xFiles_dirHeader        dirHeader;
  200.   xFiles_dirEntry        dirEntry;
  201.   xrecover_dirEntryWithName    file;
  202. } xrecover_block;
  203.  
  204. typedef enum {
  205.   xrecover_qualityEvery1024,    /* Read in 1024 byte quanta. Check each quantum
  206.                  * for dir signature. If dir, skip through it
  207.                  * If not, append to previous file quanta */
  208.   xrecover_qualityChkdsk,    /* Read in chunktable. Write out all file
  209.                  * chunks */
  210.   xrecover_qualityReadDirs,    /* Read in chunktable. Read in dir structure.
  211.                  * Attempt to find each named file. Write out
  212.                  * all orphaned file chunks */
  213.   xrecover_qualityReadDirsRaw    /* As above, but run after collecting all known
  214.                  * dirs, scan x-file for other dir chunks. */
  215. } xrecover_qualityLevel;
  216.  
  217. /* This is for return values */
  218. typedef enum {
  219.   xrecover_chunkSuccess,
  220.   xrecover_chunkDejaVu,
  221.   xrecover_chunkCantOpen,
  222.   xrecover_chunkReadFail,
  223.   xrecover_chunkWriteFail,
  224.   xrecover_chunkAttrFail
  225. } xrecover_chunkResult;
  226.  
  227. typedef enum {
  228.   xrecover_inDirLoad,
  229.   xrecover_inDirExec,
  230.   xrecover_inDirSize,
  231.   xrecover_inDirAttr,
  232.   xrecover_inDirNameLen,
  233.   xrecover_inDirName,
  234.   xrecover_inDirUnknown
  235. } xrecover_inDirWhere;        /* The ill-fated 1024 byte block dir-streamer */
  236.  
  237. typedef struct {
  238.   BOOL valid;
  239.   unsigned long offset;
  240. } xrecover_Offset;
  241.  
  242.  
  243. /* Both these strings have the property that the printf()ed output is the same
  244.  * length as the string 8-)
  245.  * The program assumes this further on 8-(
  246.  *
  247.  * (BUG - will overflow for X-Files with > 9999 entries)
  248.  */
  249. static const char *chkdskString      = "file%04d";
  250. static const char *chkdskStringMS = "FILE%04d/CHK";    /* 8-) */
  251.  
  252. static const char *xrecover_FileType( signed long load, xFiles_attr attr )
  253. {
  254.   static char buffer[10];
  255.  
  256.   if( attr & xFiles_isDir )
  257.   {
  258.     strcpy( buffer, "Directory" );
  259.   }
  260.   else
  261.   {
  262.     strcpy( buffer, "         " );
  263.  
  264.     if( ( load >> 24 ) == -1 )
  265. #ifdef __riscos
  266.     {
  267.       _kernel_swi_regs regs;
  268.       _kernel_oserror *err;
  269.  
  270.       regs.r[0] = 18;
  271.       regs.r[2] = (int) ( load >> 8 ) & 0xFFF;
  272.  
  273.       if( err = _kernel_swi( OS_FSControl, ®s, ®s ), err )
  274.       {
  275.     fprintf( stderr, "%s\n", err->errmess );
  276.       }
  277.       else
  278.       {
  279.     *( (int *) buffer ) = regs.r[2];
  280.     *( 1 + ( (int *) buffer ) ) = regs.r[3];
  281.       }
  282.     }
  283. #else
  284.     {
  285.       sprintf( buffer, "&%03lX     ", ( load >> 8 ) & 0xFFF );
  286.     }
  287. #endif
  288.   }
  289.   return buffer;
  290. }
  291.  
  292. static _kernel_oserror *mkdir( const char *dirname )
  293. #ifdef __riscos
  294. {
  295.   _kernel_swi_regs regs;
  296.  
  297.   regs.r[0] = 8;
  298.   regs.r[1] = (int) dirname;
  299.   regs.r[4] = xFiles_GROWDIRBY;    /* Why not? */
  300.  
  301.   return _kernel_swi( OS_File, ®s, ®s );
  302. }
  303. #else
  304. #error Wot no mkdir function?
  305. Should be fun on Unix [hint system( mkdir ) ]
  306. #endif
  307. /* Will have to re-do this subroutine for other OSes */
  308.  
  309. static const char *xrecover_Attr( xFiles_attr attr )
  310. {
  311.   static char buffer[8];
  312.   char *pos = buffer;
  313.  
  314.   if( attr & xFiles_isDir ) *pos++ = 'D';
  315.   if( attr & xFiles_locked ) *pos++ = 'L';
  316.   if( attr & xFiles_meRead ) *pos++ = 'R';
  317.   if( attr & xFiles_meWrite ) *pos++ = 'W';
  318.  
  319.   *pos++ = '/';
  320.  
  321.   if( attr & xFiles_youRead ) *pos++ = 'r';
  322.   if( attr & xFiles_youWrite ) *pos++ = 'w';
  323.  
  324.   *pos = '\0';
  325.  
  326.   return buffer;
  327. }
  328.  
  329. static BOOL xrecover_ReadOffset( xrecover_Offset *offset, const char *value )
  330. {
  331.   char *end;
  332.  
  333.   offset->valid = FALSE;
  334.   if( 0 == value ) return FALSE;
  335.  
  336.   offset->offset = strtoul( value, &end, 10 );
  337.  
  338.   if( end == value )
  339.   {
  340.     fprintf( stderr, "Failed to make sense of offset '%s'\n", value );
  341.     return FALSE;
  342.   }
  343.  
  344.   offset->valid = TRUE;
  345.   return TRUE;
  346. }
  347.  
  348. /* 2 functions from x-check */
  349. static int power2( int x )
  350. {
  351.   while( x && !( x & 1 ) ) x >>= 1;
  352.   return x == 1;
  353. }
  354.  
  355. static void test( int c, const char *fmt, ... )
  356. {
  357.   va_list ap;
  358.   va_start( ap, fmt );
  359.  
  360.   if( !c )
  361.   {
  362.     vfprintf( stderr, fmt, ap );
  363.   }
  364.  
  365.    va_end( ap );
  366. }
  367.  
  368. static unsigned xrecover_chunkFromOffset( const xFiles_chunk *chunkTable,
  369.                       unsigned maxChunk,
  370.                       const unsigned long offset )
  371. {
  372.   while( maxChunk-- )
  373.   {
  374.     if( chunkTable[maxChunk].usage != FREE
  375.     && chunkTable[maxChunk].offset == offset ) return maxChunk;
  376.   }
  377.   return 0;
  378. }
  379.  
  380.  
  381.  static void printChunkDir( FILE *where, const int chunkNum,
  382.                const char *pathToHere )
  383. {
  384.   fprintf( where, "Chunk %d ", chunkNum );
  385.   if( pathToHere )
  386.   {
  387.     fprintf( where, ( *pathToHere ) ? "[directory '%s'] "
  388.                     : "[root directory] ", pathToHere );
  389.   }
  390. }
  391.  
  392. static void printChunkDirEntry( FILE *where, const int chunkNum,
  393.                 const char *pathToHere, const unsigned entry,
  394.                 const char *filename, const unsigned node )
  395. {
  396.   printChunkDir( where, chunkNum, pathToHere );
  397.   fprintf( where, "entry %d ", entry );
  398.   if( filename )
  399.   {
  400.     fprintf( where, "('%s') ", filename );
  401.   }
  402.   if( node )
  403.   {
  404.     fprintf( stderr, "->[chunk %d] ", node );
  405.   }
  406. }
  407.  
  408. static unsigned roundDown( xFiles_header *header, unsigned pos )
  409. {
  410.   return pos - pos % header->allocationUnit;
  411. }
  412.  
  413. static unsigned roundUp( xFiles_header *header, unsigned pos )
  414. {
  415.   return pos - ( pos % header->allocationUnit ) + header->allocationUnit;
  416. }
  417.  
  418. static int okOffset( xFiles_header *header, unsigned offset )
  419. {
  420.    return ( offset % header->allocationUnit ) == 0;
  421. }
  422.  
  423. static int hashCmp( const void *alpha, const void *omega )
  424. {
  425.   const xFiles_dirHash *zero = alpha;
  426.   const xFiles_dirHash *inf = omega;
  427.  
  428.   return (int) zero->entryPos - (int) inf->entryPos;
  429. }
  430.  
  431. static int filenameListEntryCmp( const void *e, const void *to_the )
  432. {
  433.   xrecover_filenameListEntry *const *pi = e;
  434.   xrecover_filenameListEntry *const *i = to_the;
  435.  
  436.   return (int) (*pi)->dirEntry.size - (int) (*i)->dirEntry.size;
  437. }
  438.  
  439. static int chunkSizeCmp( const void *plus, const void *one )
  440. {
  441.   const xFiles_chunk *const *equals = plus;
  442.   const xFiles_chunk *const *zero = one;
  443.  
  444.   /* Currently this function is only used on known non-free chunks */
  445.   return (int) (*equals)->size - (int) (*zero)->size;
  446. }
  447.  
  448. static int chunkOffsetCmp( const void *alpha, const void *omega )
  449. {
  450.   /* I think it was the OED which had aardvark and zygote as its first and last
  451.    * real words.
  452.    *
  453.    * (Unfortunately, all geologists know that aa is blocky lava) */
  454.   const xFiles_chunk *const *aardvark = alpha;
  455.   const xFiles_chunk *const *zygote = omega;
  456.  
  457.   /* Get all free chunks at one end */
  458.   return (*aardvark)-> usage == FREE ? -1
  459.      : (int) (*aardvark)->offset - (int) (*zygote)->offset;
  460. }
  461.  
  462. static int xrecover_xguess( const char *suspect )
  463. {
  464.   xguess_block block;
  465.   FILE *in;
  466.   unsigned whereIsRootChunk;
  467.   long where;
  468.  
  469.   if( sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT )
  470.   {
  471.     fprintf( stderr,
  472.          "Awooga, awooga: sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT"
  473.          "\n\t\t%d != %d\n",
  474.          sizeof( xguess_block ), xFiles_ALLOCATIONUNIT );
  475.     return 255;
  476.   }
  477.  
  478.   if( 0 == ( in = fopen( suspect, "rb" ) ) )
  479.   {
  480.     fprintf( stderr, "Could not open file '%s'\n", suspect );
  481.   }
  482.   else if( 0 == fread( &block, sizeof( block ), 1, in ) )
  483.   {
  484.     fprintf( stderr, "Could not read header from file '%s'\n", suspect );
  485.   }
  486.   else
  487.   {
  488.     test( block.header.sig == xFiles_SIG,
  489.       "Missing xFiles_SIG - read %8X, should be %8X\n",
  490.       block.header.sig, xFiles_SIG );
  491.     test( block.header.hdrSize == sizeof( xFiles_header ),
  492.       "Illegal hdrSize %d\n",
  493.       block.header.hdrSize );
  494.  
  495.     test( block.header.structureVersion == xFiles_STRUCTUREVERSION,
  496.       "Illegal structureVersion %d\n",
  497.       block.header.structureVersion );
  498.  
  499.     test( block.header.directoryVersion == xFiles_DIRECTORYVERSION,
  500.       "Illegal directoryVersion %d\n",
  501.       block.header.directoryVersion );
  502.  
  503.     test( power2( block.header.allocationUnit ),
  504.       "Illegal allocationUnit %d\n",
  505.       block.header.allocationUnit );
  506.  
  507.     printf( "File '%s' header states:\n"
  508.         "         Chunktable at %d\n",
  509.         suspect, block.header.chunkTable.offset );
  510.  
  511.     whereIsRootChunk =     block.header.chunkTable.offset
  512.                + block.header.rootChunk * sizeof( xFiles_chunk );
  513.  
  514.     if( fseek( in, whereIsRootChunk, SEEK_SET ) )
  515.     {
  516.       fprintf( stderr, "Could not seek to root chunk at %d\n",
  517.            whereIsRootChunk );
  518.     }
  519.     else if( 0 == fread( &block, sizeof( xFiles_chunk ), 1, in ) )
  520.     {
  521.       fprintf( stderr, "Could not read root directory chunk at %d\n",
  522.            whereIsRootChunk );
  523.     }
  524.     else
  525.     {
  526.       printf( "Root directory is in chunk %d, file offset %d\n",
  527.           block.header.rootChunk, block.chunk.offset );
  528.     }
  529.   }
  530.  
  531.   rewind( in );
  532.  
  533.   while( ( where = ftell( in ) ),
  534.      ( 0 != fread( &block, sizeof( block ), 1, in ) ) )
  535.   {
  536.     if(       ( block.chunk.offset == where )
  537.     && ( block.chunk.size % sizeof( xFiles_chunk ) == 0 ) )
  538.     {
  539.       /* Smells like the chunktable if the first 'chunk' points to this
  540.        * position in the file (which is read into where before the chunktable)
  541.        * and the size of the 'chunktable' is a multiple of the size of a chunk
  542.        */
  543.        printf( "Could be chunktable at %ld\n", where );
  544.     }
  545.     else if(    ( block.dirHeader.sig == xFiles_DIRSIG )
  546.          && ( block.dirHeader.parent == 0 ) )
  547.     {
  548.       /* Smells like the root dir if there is a dir signature ('ANDY') and
  549.        * the parent dir is 0
  550.        * [So what's wrong with using 'Nick' as a dir signature 8-) ]
  551.        */
  552.        printf( "Could be root directory at                  %ld\n", where );
  553.     }
  554.   }
  555.  
  556.   return 0;
  557. }
  558.  
  559. static void xrecover_clearList( const unsigned chunkNum,
  560.                 xrecover_chunkTableCrossCheck *crossCheckEntry,
  561.                 xrecover_filenameListEntry *fileTree,
  562.                 int verbose )
  563. {
  564.   if( verbose > 0 )
  565.   {
  566.     printf( "Unlinking: chunk %d currently has %ld reference(s)\n", chunkNum,
  567.         crossCheckEntry->references );
  568.   }
  569.   while( fileTree )
  570.   {
  571.     if( fileTree->node == chunkNum )
  572.     {
  573.       fprintf( stderr, "Unlinking '%s' from chunk %d\n",
  574.            ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ),
  575.            chunkNum );
  576.       if( ( fileTree->status & ~xrecover_crossLinked ) == 0 )
  577.       {
  578.     fputs( "Wasn't cross linked", stderr );
  579.       }
  580.       /* Clear these flags */
  581.       fileTree->status &= ~( xrecover_foundChunk | xrecover_sizeMatches
  582.                  | xrecover_crossLinked );
  583.  
  584.       if( crossCheckEntry->references-- == 0 )
  585.       {
  586.     fprintf( stderr, "Chunk %d reference count incorrect\n", chunkNum );
  587.     crossCheckEntry->references = 0;
  588.       }
  589.     }
  590.  
  591.     fileTree = fileTree->next;
  592.   }
  593.   if( verbose > 0 )
  594.   {
  595.     printf( "                          now has %ld reference(s)\n",
  596.         crossCheckEntry->references );
  597.   }
  598.  
  599.   crossCheckEntry->status &= ~xrecover_crossLinked;
  600.   /* As it's clear it can't be cross linked */
  601. }
  602.  
  603. static void xrecover_printList( FILE *where,
  604.                 const xFiles_chunk *chunkTable,
  605.                 const xrecover_chunkTableCrossCheck
  606.                   *crossCheckTable,
  607.                 const xrecover_filenameListEntry *fileTree,
  608.                 int verbose )
  609. {
  610.   while( fileTree )
  611.   {
  612.     if( verbose == 0 )
  613.     {
  614.       fprintf( where, "%s\n",
  615.            ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
  616.     }
  617.     else
  618.     {
  619.       fprintf( where, "%08X %08X %8d %-7s %s %s\n", fileTree->dirEntry.load,
  620.            fileTree->dirEntry.exec, fileTree->dirEntry.size,
  621.            xrecover_Attr( fileTree->dirEntry.attr ),
  622.            xrecover_FileType( fileTree->dirEntry.load,
  623.                   fileTree->dirEntry.attr ),
  624.            ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
  625.       if( verbose > 1 )
  626.       {
  627.     if( fileTree->status & xrecover_foundChunk )
  628.     {
  629.       fprintf( where, "  chunk %d ", fileTree->node );
  630.       fprintf( where,
  631.            ( chunkTable[fileTree->node].size
  632.               == fileTree->dirEntry.size )
  633.            ? "size=%d" : "chunk.size=%d, dirEntry.size=%d",
  634.            chunkTable[fileTree->node].size, fileTree->dirEntry.size );
  635.       if( ( chunkTable[fileTree->node].size == fileTree->dirEntry.size )
  636.         == !!( fileTree->status & xrecover_sizeMatches ) )
  637.       {
  638.         fputc( '\n', where );
  639.       }
  640.       else
  641.       {
  642.         fputs( " [incorrectly flagged]\n", where );
  643.       }
  644.       if( fileTree->status & xrecover_crossLinked )
  645.       {
  646.         if( 1 == crossCheckTable[fileTree->node].references )
  647.         {
  648.           fputs( "  error - flagged as cross linked, but chunk has exactly "
  649.              "one reference\n", where );
  650.         }
  651.         else
  652.         {
  653.           fprintf( where, "  cross linked: total %ld cross references\n",
  654.                crossCheckTable[fileTree->node].references );
  655.         }
  656.       }
  657.       else
  658.       {
  659.         if( 1 != crossCheckTable[fileTree->node].references )
  660.         {
  661.           fprintf( where, "  error - not flagged as cross linked, chunk has"
  662.                " %ld cross references\n",
  663.                crossCheckTable[fileTree->node].references );
  664.         }
  665.       }
  666.     }
  667.     else
  668.     {
  669.       fputs( "  [no chunk found]\n", where );
  670.     }
  671.       }
  672.     }
  673.     fileTree = fileTree->next;
  674.   }
  675. }
  676.  
  677. static void
  678. xrecover_getProblemList( xrecover_filenameListEntry *fileTree,
  679.              xrecover_filenameListEntry **problems,
  680.              unsigned numProblems )
  681. {
  682.   xrecover_filenameListEntry **end = problems + numProblems;
  683.   while( fileTree )
  684.   {
  685.     if( ( fileTree->status & xrecover_foundChunk ) == 0 )
  686.     {
  687.       *problems = fileTree;
  688.       if( ++problems > end )
  689.       {
  690.     fputs( "Miscounted problems in the fileTree\n", stderr );
  691.     return;
  692.       }
  693.     }
  694.     fileTree = fileTree->next;
  695.   }
  696. }
  697.  
  698. static void xrecover_freeList( xrecover_filenameListEntry **fileTree )
  699. {
  700.   xrecover_filenameListEntry *temp;
  701.   while( *fileTree )
  702.   {
  703.     temp = (*fileTree)->next;
  704.     free( *fileTree );
  705.     *fileTree = temp;
  706.   }
  707. }
  708.  
  709. static xrecover_chunkResult
  710. xrecover_recoverDir( FILE *in, FILE *out, const char *victim,
  711.              const char *pathToHere,
  712.              const unsigned long start,
  713.              const unsigned chunkNum,
  714.              const xFiles_chunk *thisChunk,
  715.              const xFiles_chunk *chunkTable,
  716.              xrecover_chunkTableCrossCheck *crossCheckTable,
  717.              xrecover_filenameListEntry **fileTree,
  718.              const unsigned maxChunk, int verbose )
  719. {
  720.   /* out != NULL for text output
  721.    * pathToHere != NULL for full recursion
  722.    * if dirHeader == 0 need to read it from file
  723.    * start points to file offset of start of directory (good for seeking)
  724.    */
  725.   xrecover_block buffer;
  726.   xFiles_dirHeader header;
  727.   unsigned dirEntry = 0;
  728.   size_t maxSize;
  729.   unsigned long where = -1L;    /* ANSI's error result from ftell() */
  730.   xFiles_dirHash *dirHash, *currentDirHash, *dirHashLimit;
  731.   unsigned bytesRead, bytesToRead;
  732.   BOOL haveHash = FALSE;
  733.   const size_t pathLen = pathToHere ? ( 1 + strlen( pathToHere )
  734.                     + strlen( xrecover_dirSep ) ) : 0;
  735.   /* One for the '\0' */
  736.  
  737.  
  738.   if( thisChunk == 0 && chunkTable && chunkNum )
  739.   {
  740.     /* chunk 0 is always the chunkTable, so it can never be a directory */
  741.     thisChunk = chunkTable + chunkNum;
  742.   }
  743.  
  744.   if( chunkNum && crossCheckTable &&
  745.       ( crossCheckTable[chunkNum].status & xrecover_dejaVu ) )
  746.   {
  747.     printChunkDir( stderr, chunkNum, pathToHere );
  748.     fputs( "Déjà vu - been to this directory before", stderr );
  749.     if( crossCheckTable[chunkNum].file )
  750.     {
  751.       fprintf( stderr, " as '%s'\n",
  752.            ( (char *) &(crossCheckTable[chunkNum].file->dirEntry) )
  753.          + sizeof( xFiles_dirEntry ) );
  754.     }
  755.     else putc( '\n', stderr );
  756.  
  757.     if( out ) fclose( out );
  758.     return xrecover_chunkDejaVu;
  759.   }
  760.  
  761.   rewind( in );
  762.   if( fseek( in, start, SEEK_SET ) )
  763.   {
  764.     printChunkDir( stderr, chunkNum, pathToHere );
  765.     fprintf( stderr, "could not seek to start (offset %ld)\n", start );
  766.     if( out ) fclose( out );
  767.     return xrecover_chunkReadFail;
  768.   }
  769.  
  770.   if( 0 == fread( &header, sizeof( xFiles_dirHeader ), 1, in ) )
  771.   {
  772.     printChunkDir( stderr, chunkNum, pathToHere );
  773.     fputs( "could not read dirHeader\n", stderr );
  774.     if( out ) fclose( out );
  775.     return xrecover_chunkReadFail;
  776.   }
  777.  
  778.  
  779.   if( verbose > 1 )
  780.   {
  781.     fputs( "Directory", stdout );
  782.     /* This has to be fputs() as puts() appends '\n'
  783.      * Why are puts, fputs and *printf so inconsistent? */
  784.     if( pathToHere )
  785.     {
  786.       printf( " '%s'", *pathToHere ? pathToHere : "<Root>" );
  787.     }
  788.     printf( ": size=%d used=%d\n", header.size, header.used );
  789.   }
  790.  
  791.   if( 0 == ( dirHash = malloc( sizeof( xFiles_dirHash ) * header.used ) ) )
  792.   {
  793.     printChunkDir( stderr, chunkNum, pathToHere );
  794.     fprintf( stderr, "failed to get %d bytes of memory for dir hash\n",
  795.          sizeof( xFiles_dirHash ) * header.used );
  796.     if( out ) fclose( out );
  797.     return xrecover_chunkReadFail;
  798.   }
  799.  
  800.   bytesRead = fread( dirHash, sizeof( xFiles_dirHash ), header.used, in );
  801.   if( bytesRead != header.used )
  802.   {
  803.     printChunkDir( stderr, chunkNum, pathToHere );
  804.     fprintf( stderr, "could only read %d of %d dirHash(es)\n",
  805.          bytesRead, header.used );
  806.     if( out ) fclose( out );
  807.     free( dirHash );
  808.     return xrecover_chunkReadFail;
  809.   }
  810.   /* Sort into position order. As far as I can tell X-Files 0·56 keeps its
  811.    * hash sorted by position order, but this doesn't seem to be part of the
  812.    * specification, so I won't assume it */
  813.  
  814.   qsort( dirHash, header.used, sizeof( xFiles_dirHash ), hashCmp );
  815.  
  816.   currentDirHash = dirHash;
  817.   dirHashLimit = dirHash + header.used;
  818.  
  819.   if( fseek( in, sizeof( xFiles_dirHash ) * ( header.size - header.used ),
  820.          SEEK_CUR ) )
  821.   {
  822.     printChunkDir( stderr, chunkNum, pathToHere );
  823.     fputs( "could not seek to start of dir entries\n", stderr );
  824.     if( out ) fclose( out );
  825.     free( dirHash );    /* Something says that putting goto exit; would
  826.              * reduce the possibility of bugs (in this case a
  827.              * memory leak on dirHash and an open file on out ) */
  828.     return xrecover_chunkReadFail;
  829.   }
  830.  
  831.   for( ; dirEntry < header.used; dirEntry++ )
  832.   {
  833.     where = ftell( in );
  834.     if( haveHash ) currentDirHash++;
  835.     /* Used currentDirHash, so advance it one */
  836.     if( 0 == fread( &buffer, sizeof( xFiles_dirEntry ), 1, in ) )
  837.     {
  838.       printChunkDir( stderr, chunkNum, pathToHere );
  839.       fprintf( stderr, "could not read directory entry %d\n",
  840.            dirEntry );
  841.     }
  842.     else
  843.     {
  844.       if( buffer.dirEntry.nameLen > xFiles_MAXNAME )
  845.       {
  846.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
  847.     fprintf( stderr, "reports nameLen as %d - truncating to %d\n",
  848.          buffer.dirEntry.nameLen, xFiles_MAXNAME );
  849.     buffer.dirEntry.nameLen = xFiles_MAXNAME;
  850.       }
  851.  
  852.       bytesToRead = _four( buffer.dirEntry.nameLen + 1 );
  853.       bytesRead = fread( &(buffer.file.name), 1, bytesToRead, in );
  854.       if( bytesRead < bytesToRead )
  855.       {
  856.     /* Terminate what we got */
  857.     buffer.file.name[bytesRead] = '\0';
  858.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  859.                 buffer.file.name, 0 );
  860.     fprintf( stderr, "could not read %d bytes of filename (Got %d)\n",
  861.          bytesToRead, bytesRead );
  862.       }
  863.  
  864.       while( ( currentDirHash < dirHashLimit )
  865.          && ( currentDirHash->entryPos < ( where - start ) ) )
  866.       {
  867.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
  868.     fprintf( stderr, "no filename entry for dirhash:\n"
  869.          "\t\t'%c%c%c%c' chunk %d entryPos %d\n",
  870.          currentDirHash->nameStart[0], currentDirHash->nameStart[1],
  871.          currentDirHash->nameStart[2], currentDirHash->nameStart[3],
  872.          currentDirHash->node, currentDirHash->entryPos );
  873.     currentDirHash++;
  874.       }
  875.  
  876.       if( ( currentDirHash >= dirHashLimit )
  877.       || ( currentDirHash->entryPos != ( where - start ) ) )
  878.       {
  879.     haveHash = FALSE;
  880.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  881.                 buffer.file.name, 0 );
  882.     fputs( "no dirhash for filename entry\n", stderr );
  883.       }
  884.       else
  885.       {
  886.     haveHash = TRUE;
  887.     if( *( (int *) currentDirHash ) != *( (int *) buffer.file.name ) )
  888.     {
  889.     /* Check hash == name */
  890.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  891.                 buffer.file.name, 0 );
  892.     fprintf( stderr, "does not match dirhash '%c%c%c%c'\n",
  893.          currentDirHash->nameStart[0], currentDirHash->nameStart[1],
  894.          currentDirHash->nameStart[2], currentDirHash->nameStart[3] );
  895.     }
  896.       }
  897.  
  898.       where += sizeof( xFiles_dirEntry ) + bytesToRead;
  899.       /* Where we *should* be */
  900.       if( out )
  901.       {
  902.     fprintf( out, "%08X %08X %8d %-7s %s %s\n", buffer.dirEntry.load,
  903.            buffer.dirEntry.exec, buffer.dirEntry.size,
  904.            xrecover_Attr( buffer.dirEntry.attr ),
  905.            xrecover_FileType( buffer.dirEntry.load,
  906.                       buffer.dirEntry.attr ),
  907.            buffer.file.name );
  908.       }
  909.  
  910.       if( fileTree && pathToHere )
  911.       {
  912.     xrecover_filenameListEntry *current =
  913.       malloc( sizeof( xrecover_filenameListEntry )
  914.               + buffer.dirEntry.nameLen + pathLen );
  915.     char *entryPath = current ? ( (char *) &(current->dirEntry) )
  916.                     + sizeof( xFiles_dirEntry )
  917.                   : 0;
  918.     /* Relative path from x-file root to this entry */
  919.  
  920.     if( 0 == current ) continue;    /* Implicit goto the next loop */
  921.  
  922.     current->dirEntry = buffer.dirEntry;
  923.     if( *pathToHere )
  924.     {
  925.       /* Make a path */
  926.       sprintf( entryPath, "%s%s%s", pathToHere, xrecover_dirSep,
  927.            buffer.file.name );
  928.     }
  929.     else
  930.     {
  931.       strcpy( entryPath, buffer.file.name );
  932.     }
  933.     current->status = xrecover_touched;
  934.     current->node = haveHash ? currentDirHash->node : 0;
  935.  
  936.     if( current->node )
  937.     {
  938.       if( current->node >= maxChunk )
  939.       {
  940.         printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  941.                 buffer.file.name, 0 );
  942.         fprintf( stderr, "chunk %d is out of range (%d)\n", current->node,
  943.              maxChunk );
  944.         current->node = 0;
  945.       }
  946.       else
  947.       {
  948.         if( chunkTable[current->node].usage == FREE )
  949.         {
  950.            printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  951.                    buffer.file.name, current->node );
  952.            fputs( "is marked free\n", stderr );
  953.            current->node = 0;
  954.         }
  955.         else
  956.         {
  957.           current->status |= xrecover_foundChunk;
  958.           /* So we have a chunk... */
  959.           if( crossCheckTable[current->node].file )
  960.           {
  961.         xrecover_filenameListEntry *crossLink =
  962.           (crossCheckTable[current->node].file);
  963.         printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  964.                     buffer.file.name, current->node );
  965.         fprintf( stderr, "is cross linked with file '%s'\n",
  966.              ( (char *) &(crossLink->dirEntry) )
  967.              + sizeof( xFiles_dirEntry ) );
  968.         /* Eeek. But wait for it */
  969.  
  970.         if( crossLink->status & xrecover_sizeMatches )
  971.         {
  972.           /* They think that they have their chunk */
  973.           if( chunkTable[current->node].size == buffer.dirEntry.size )
  974.           {
  975.             /* And so do we */
  976.             crossLink->status |= xrecover_crossLinked;
  977.             current->status |= xrecover_crossLinked
  978.                        | xrecover_sizeMatches;
  979.             crossCheckTable[current->node].references += 1;
  980.           }
  981.           else
  982.           {
  983.             /* They match and we don't */
  984.             current->status &= ~xrecover_foundChunk;
  985.             fprintf( stderr, "File '%s' size %d tallies with "
  986.                  "chunktable - unlinking '%s'\n",
  987.                  ( (char *) &(crossLink->dirEntry) )
  988.                  + sizeof( xFiles_dirEntry ),
  989.                  chunkTable[current->node].size, buffer.file.name );
  990.             current->node = 0;
  991.           }
  992.         }
  993.         else
  994.         {
  995.           /* Their size doesn't match */
  996.           if( chunkTable[current->node].size == buffer.dirEntry.size )
  997.           {
  998.             /* But ours does */
  999.             current->status |= xrecover_sizeMatches;
  1000.             crossCheckTable[current->node].file = current;
  1001.             xrecover_clearList( current->node,
  1002.                     crossCheckTable + current->node,
  1003.                     *fileTree, verbose );
  1004.  
  1005.             if( crossCheckTable[current->node].references )
  1006.             {
  1007.               fprintf( stderr, "Awooga - failed to clear the list - %ld"
  1008.                    " references remain",
  1009.                    crossCheckTable[current->node].references );
  1010.               current->status |= xrecover_crossLinked;
  1011.             }
  1012.             else
  1013.             {
  1014.               crossCheckTable[current->node].references = 1;
  1015.             }
  1016.           }
  1017.           else
  1018.           {
  1019.             /* Our size doesn't match either */
  1020.             crossLink->status |= xrecover_crossLinked;
  1021.             current->status |= xrecover_crossLinked;
  1022.             crossCheckTable[current->node].references += 1;
  1023.           }
  1024.         }
  1025.  
  1026.           }
  1027.           else
  1028.           {
  1029.         /* We have the chunk all to ourselves */
  1030.         if( chunkTable[current->node].size == buffer.dirEntry.size )
  1031.         {
  1032.           /* And the size matched 8-) */
  1033.           current->status |= xrecover_sizeMatches;
  1034.         }
  1035.         crossCheckTable[current->node].file = current;
  1036.         crossCheckTable[current->node].references = 1;
  1037.           }
  1038.  
  1039.           if( buffer.dirEntry.attr & xFiles_isDir )
  1040.           {
  1041.         size_t size = victim ? strlen( victim )
  1042.                        + strlen( xrecover_dirSep )
  1043.                        + strlen( entryPath ) + 1
  1044.                      : 0;
  1045.         char *tempBuffer = size ? malloc( size ) : 0;
  1046.         _kernel_oserror *err = 0;
  1047.  
  1048.         if( size )
  1049.         {
  1050.           if( tempBuffer == 0 )
  1051.           {
  1052.             printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1053.                     buffer.file.name, current->node );
  1054.             fprintf( stderr, "failed to get %d bytes of memory for dir "
  1055.                  "name.\nCannot create output sub-directory, so "
  1056.                  "will not recurse into input sub-directory\n",
  1057.                  size );
  1058.             err = (_kernel_oserror *) -5;
  1059.             /* Should address exception/abort on data transfer if anyone
  1060.              * is silly enough to try to read errmess
  1061.              */
  1062.             current->status |= xrecover_started;
  1063.             /* Flag to stop list routine stamping all over any offending
  1064.              * file that got in our way */
  1065.           }
  1066.           else
  1067.           {
  1068.             sprintf( tempBuffer, "%s%s%s", victim, xrecover_dirSep,
  1069.                  entryPath );
  1070.           }
  1071.         }
  1072.         if( size && ( 0 != ( err = mkdir( tempBuffer ) ) ) )
  1073.         {
  1074.           printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1075.                       buffer.file.name, current->node );
  1076.           fprintf( stderr, "%s\n", err->errmess );
  1077.         }
  1078.         free( tempBuffer );
  1079.  
  1080.         if( 0 == err )
  1081.         {
  1082.           xrecover_recoverDir( in, out, victim,
  1083.                        entryPath,
  1084.                        chunkTable[current->node].offset,
  1085.                        current->node, 0, chunkTable,
  1086.                        crossCheckTable, fileTree,
  1087.                        maxChunk, verbose );
  1088.           rewind( in );    /* Clear any error */
  1089.           if( fseek( in, where, SEEK_SET ) )
  1090.           {
  1091.             printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1092.                     buffer.file.name, current->node );
  1093.             fputs( "could not seek ready for next directory entry\n",
  1094.                stderr );
  1095.             if( out ) fclose( out );
  1096.             free( dirHash );
  1097.             current->next = *fileTree;
  1098.             fileTree = ¤t;
  1099.             return xrecover_chunkReadFail;
  1100.           }
  1101.         }
  1102.           }
  1103.         } /* End of having a chunk */
  1104.       }
  1105.     }
  1106.     else
  1107.     {
  1108.       printChunkDir( stderr, chunkNum, pathToHere );
  1109.       fprintf( stderr, "no chunk found for '%s'\n", buffer.file.name );
  1110.     }
  1111.  
  1112.     /* Add this filename to the head of the list */
  1113.     current->next = *fileTree;
  1114.     *fileTree = current;
  1115.       }
  1116.     }
  1117.   }
  1118.  
  1119.   if( where != -1L && start != -1L && thisChunk
  1120.       && ( ( maxSize = (unsigned int) ( where - start ) ) > thisChunk->size ) )
  1121.   {
  1122.     printChunkDir( stderr, chunkNum, pathToHere );
  1123.     fprintf( stderr, "chunkTable reports size as %d, appears to be %d\n",
  1124.          thisChunk->size, maxSize );
  1125.   }
  1126.   if( out ) fclose( out );
  1127.   free( dirHash );
  1128.   return xrecover_chunkSuccess;
  1129. }
  1130.  
  1131. static xrecover_chunkResult
  1132. xrecover_recoverArbitaryArea( FILE *in, const char *victim,
  1133.                   const unsigned long offset,
  1134.                   unsigned long size, const int verbose )
  1135. {
  1136.   xrecover_chunkResult status = xrecover_chunkSuccess;
  1137.   FILE *out;
  1138.  
  1139.   rewind( in );    /* Seems to need this to reset stream after seek beyond end */
  1140.   if( fseek( in, offset, SEEK_SET ) )
  1141.   {
  1142.     fprintf( stderr, "Could not seek to offset %ld\n", offset );
  1143.     return xrecover_chunkReadFail;
  1144.   }
  1145.  
  1146.   remove( victim );    /* This will ensure the correct file type (Data)
  1147.              * and that we can open R/ files */
  1148.  
  1149.   if( 0 == ( out  = fopen( victim, "wb" ) ) )
  1150.   {
  1151.     fprintf( stderr, "Offset %ld could not open file '%s' for output\n",
  1152.          offset, victim ? victim : "<NULL POINTER>" );
  1153.     return xrecover_chunkCantOpen;
  1154.   }
  1155.  
  1156.  
  1157.   while( size )
  1158.   {
  1159.     char buffer[xrecover_bufferSize];
  1160.     unsigned bytesWritten;
  1161.     unsigned bytesToRead = (unsigned) _min( size, sizeof( buffer ) );
  1162.     unsigned bytesRead;
  1163.  
  1164.     memset( buffer, 0, bytesToRead );
  1165.  
  1166.     bytesRead = fread( buffer, 1, bytesToRead, in );
  1167.     if( bytesRead != bytesToRead )
  1168.     {
  1169.       if( status == xrecover_chunkSuccess )
  1170.       {
  1171.     status = xrecover_chunkReadFail;
  1172.       }
  1173.       fprintf( stderr, "Offset %ld could not read %d bytes (Got %d)\n",
  1174.            offset, bytesToRead, bytesRead );
  1175.       if( bytesRead == 0 )
  1176.       {
  1177.     if( verbose > 0 ) puts( "Bailing out" );
  1178.     break;
  1179.       }
  1180.       bytesRead = bytesToRead;    /* Pretend that we got them */
  1181.     }
  1182.  
  1183.     {
  1184.       bytesWritten = fwrite( buffer, 1, bytesRead, out );
  1185.       if( bytesRead != bytesWritten )
  1186.       {
  1187.     fprintf( stderr, "Offset %ld, file '%s' - only wrote %d byte(s) out of "
  1188.          "%d\n", offset, victim, bytesWritten, bytesRead );
  1189.     if( status == xrecover_chunkSuccess )
  1190.     {
  1191.       status = xrecover_chunkWriteFail;
  1192.     }
  1193.       }
  1194.     size -= bytesRead;
  1195.     }
  1196.   }
  1197.  
  1198.   fclose( out );
  1199.   return status;
  1200. }
  1201.  
  1202. static xrecover_chunkResult
  1203. xrecover_recoverChunkChkdsk( FILE *in, const char *victim, unsigned chunkNum,
  1204.                  const xFiles_chunk *chunk, unsigned forceSize,
  1205.                  BOOL dirsAsText, BOOL suppressFiles, int verbose )
  1206. /* Leave forceSize as 0 to believe the chunkTable entry
  1207.  * Otherwise, you take your life into your own hands!
  1208.  */
  1209. {
  1210.   FILE *out = 0;
  1211.   char buffer[xrecover_bufferSize];
  1212.   unsigned bytesToCopy = forceSize ? forceSize : chunk->size;
  1213.   unsigned bytesRead, bytesWritten;
  1214.   unsigned bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
  1215.   BOOL isDir = FALSE;
  1216.   xrecover_chunkResult status = xrecover_chunkSuccess;    /* optimism */
  1217.  
  1218.   rewind( in );    /* Seems to need this to reset stream after seek beyond end */
  1219.   if( fseek( in, chunk->offset, SEEK_SET ) )
  1220.   {
  1221.     fprintf( stderr, "Chunk %d could not seek to start (offset %d)\n", chunkNum,
  1222.          chunk->offset );
  1223.     return xrecover_chunkReadFail;
  1224.   }
  1225.  
  1226.   /* printf( "off=%d f=%d c=%d using %d\n", chunk->offset, forceSize, chunk->size, bytesToCopy ); */
  1227.   if( verbose > 3 )
  1228.   {
  1229.     printf( "Sought %d, got to %ld\n", chunk->offset, ftell( in ) );
  1230.   }
  1231.  
  1232.   memset( buffer, 0, sizeof( buffer ) );
  1233.  
  1234.   bytesRead = fread( buffer, 1, bytesToRead, in );
  1235.   if( bytesRead < bytesToRead )
  1236.   {
  1237.     fprintf( stderr, "Chunk %d could not read %d bytes (Got %d)\n",
  1238.          chunkNum, sizeof( buffer ), bytesRead );
  1239.     return xrecover_chunkReadFail;
  1240.   }
  1241.  
  1242.   if( *(int *) buffer == xFiles_DIRSIG && dirsAsText )
  1243.   {
  1244.     /* Smells like a directory. */
  1245.     isDir = TRUE;
  1246.   }
  1247.   else if( suppressFiles ) return xrecover_chunkSuccess;
  1248.  
  1249.   if( victim )
  1250.   {
  1251.     remove( victim );    /* This will ensure the correct file type (Text or Data)
  1252.              * and that we can open R/ files */
  1253.     out = fopen( victim, isDir ? "w": "wb" );
  1254.   }
  1255.  
  1256.   if( ( victim != 0 ) && ( out == 0 ) )
  1257.   {
  1258.     status = xrecover_chunkCantOpen;
  1259.     fprintf( stderr, "Chunk %d could not open file '%s' for output\n", chunkNum,
  1260.          victim );
  1261.   }
  1262.  
  1263.   if( isDir )
  1264.   {
  1265.     /* It did use to pass in the header, with code to read the header in
  1266.      * xrecover_recoverDir() if a NULL pointer was passed, but I figured that
  1267.      * the amount of code generated was probably greater than the time saved for
  1268.      * a buffered filing system re-reading 16 bytes! */
  1269.     return xrecover_recoverDir( in, out, 0, 0, chunk->offset, chunkNum, chunk,
  1270.                 0, 0, 0, 0, verbose );
  1271.   }
  1272.  
  1273.   if( bytesRead > bytesToCopy ) bytesRead = bytesToCopy;
  1274.   /* Cope with short files */
  1275.  
  1276.   while( bytesToCopy )
  1277.   {
  1278.     /* I was going to write a stream based dir converter, but then I thought:
  1279.      * Sod it, let's just malloc a buffer big enough.
  1280.     if( isDir )
  1281.     {
  1282.  
  1283.     }
  1284.     else
  1285.      */
  1286.     {
  1287.       if( out )
  1288.       {
  1289.     bytesWritten = fwrite( buffer, 1, bytesRead, out );
  1290.     if( bytesRead != bytesWritten )
  1291.     {
  1292.       fprintf( stderr, "Chunk %d, file '%s' - only wrote %d byte(s) out of "
  1293.            "%d\n", chunkNum, victim, bytesWritten, bytesRead );
  1294.       if( status == xrecover_chunkSuccess )
  1295.       {
  1296.         status = xrecover_chunkWriteFail;
  1297.       }
  1298.     }
  1299.       }
  1300.     }
  1301.     bytesToCopy -= bytesRead;
  1302.     if( bytesToCopy != 0 )
  1303.     {
  1304.       bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
  1305.  
  1306.  
  1307.       memset( buffer, 0, bytesToRead );
  1308.       bytesRead = fread( buffer, 1, bytesToRead, in );
  1309.       if( bytesRead != bytesToRead )
  1310.       {
  1311.     fprintf( stderr, "Chunk %d could not read %d bytes (Got %d)\n",
  1312.          chunkNum, bytesToRead, bytesRead );
  1313.     if( ferror( in ) )
  1314.     {
  1315.       perror( "Error on input x-file" );
  1316.     }
  1317.     else
  1318.     {
  1319.       fprintf( stderr, "Reached end of input x-file - now at %ld\n",
  1320.            ftell( in ) );
  1321.     }
  1322.     if( status == xrecover_chunkSuccess )
  1323.     {
  1324.       status = xrecover_chunkReadFail;
  1325.     }
  1326.     if( bytesRead == 0 )
  1327.     {
  1328.       if( verbose > 0 ) puts( "Bailing out" );
  1329.       break;
  1330.     }
  1331.     bytesRead = bytesToRead;    /* Pretend that we got them */
  1332.       }
  1333.     }
  1334.  
  1335.   }
  1336.   if( out ) fclose( out );
  1337.   return status;
  1338. }
  1339.  
  1340.  
  1341. static unsigned
  1342. xrecover_doGoodInList( FILE *in, const char *victim,
  1343.                const xFiles_chunk *chunkTable,
  1344.                xrecover_chunkTableCrossCheck *crossCheckTable,
  1345.                xrecover_filenameListEntry *fileTree,
  1346.                const BOOL useAlloc, const int verbose )
  1347. {
  1348.   unsigned problemFiles = 0;
  1349.   xrecover_chunkResult result;
  1350.   size_t prefixNameLen = strlen( victim ) + strlen( xrecover_dirSep ) + 1;
  1351.  
  1352.   while( fileTree )
  1353.   {
  1354.     if( fileTree->status & xrecover_foundChunk )
  1355.     {
  1356.       if( victim && 0 == ( fileTree->status & xrecover_started ) )
  1357.       {
  1358.     char *outPathname;
  1359.     const xFiles_chunk *thisChunk = chunkTable + fileTree->node;
  1360.     unsigned getSize = 0;    /* Believe the chunk by default */
  1361.     const char *filename = ( (char *) &(fileTree->dirEntry) )
  1362.                    + sizeof( xFiles_dirEntry );
  1363.  
  1364.     if( !( fileTree->status & xrecover_sizeMatches )
  1365.         && !( fileTree->dirEntry.attr & xFiles_isDir ) )
  1366.     /* Already flagged problem directories
  1367.      * directories report size in parent as 0, but have non-zero size in the
  1368.      * chunktable, hence they will always error here */
  1369.     {
  1370.       /* Which has it bigger - the directory or the chunk? */
  1371.       getSize = _max( fileTree->dirEntry.size, thisChunk->size );
  1372.  
  1373.       fprintf( stderr, "File '%s' chunk.size=%d, dirEntry.size=%d "
  1374.            "chunk.allocSize=%d\n"
  1375.            "Will try to recover %d%s\n", filename, thisChunk->size,
  1376.            fileTree->dirEntry.size, thisChunk->allocSize,
  1377.            getSize, ( getSize > thisChunk->allocSize )
  1378.                   ? " [which is greater than the allocated size]"
  1379.                   : "" );
  1380.     }
  1381.  
  1382.     if( useAlloc ) getSize = _max( getSize, thisChunk->allocSize );
  1383.  
  1384.     outPathname = malloc( prefixNameLen + strlen( filename ) );
  1385.  
  1386.     if( outPathname == 0 )
  1387.     {
  1388.       fprintf( stderr, "Could not get buffer for file '%s' - will add chunk"
  1389.            " %d to unclaimed list\n", filename, fileTree->node );
  1390.       result = xrecover_chunkCantOpen;
  1391.     }
  1392.     else
  1393.     {
  1394.       sprintf( outPathname, "%s%s%s", victim, xrecover_dirSep, filename );
  1395.       /* Go for it */
  1396.       result = ( fileTree->dirEntry.attr & xFiles_isDir )
  1397.            ? xrecover_chunkSuccess    /* Directory is already done */
  1398.            : xrecover_recoverChunkChkdsk( in, outPathname,
  1399.                           fileTree->node, thisChunk,
  1400.                           getSize, FALSE, FALSE,
  1401.                           verbose );
  1402.     }
  1403.  
  1404.     switch( result )
  1405.     {
  1406.       case xrecover_chunkSuccess:
  1407.       case xrecover_chunkWriteFail:
  1408.       /* Not my problem if your media won't write */
  1409.       {
  1410.         fileTree->status |= xrecover_readWhole;
  1411.  
  1412. #ifdef __riscos
  1413.         if( 0 == ( fileTree->status & xrecover_openFail ) )
  1414.         {
  1415.           _kernel_swi_regs regs;
  1416.           _kernel_oserror *err;
  1417.           regs.r[0] = 1;
  1418.           regs.r[1] = (int) outPathname;
  1419.           regs.r[2] = fileTree->dirEntry.load;
  1420.           regs.r[3] = fileTree->dirEntry.exec;
  1421.           regs.r[5] = fileTree->dirEntry.attr & 0xff;
  1422.  
  1423.           err = _kernel_swi( OS_File, ®s, ®s );
  1424.           if( err )
  1425.           {
  1426.         fprintf( stderr, "%s\n", err->errmess );
  1427.           }
  1428.         }
  1429. #endif
  1430.       }
  1431.       break;
  1432.  
  1433.       case xrecover_chunkCantOpen:
  1434.       {
  1435.         /* This chunk becomes one of the list of unrecovered chunks */
  1436.         fileTree->status |= xrecover_openFail;
  1437.         /* Make sure that we don't try to re-link this file with its chunk
  1438.          */
  1439.         if( 0 == --(crossCheckTable[fileTree->node].references) )
  1440.         {
  1441.           crossCheckTable[fileTree->node].file = NULL;
  1442.         }
  1443.         /* OK. Not beautiful - ideally each chunk should have a linked list
  1444.          * (or variable array - time for genArray.c to meet the outside
  1445.          * world?) of files that (cross)link to it, but ideally there should
  1446.          * be no cross links.
  1447.          *
  1448.          * If you'd like a copy of genArray.c, email <bagpuss@done.net>
  1449.          */
  1450.  
  1451.  
  1452.         /* ++problemFiles; */
  1453.         /* Don't need this, as *this file* wasn't the problem, rather it was
  1454.          * the output media
  1455.          */
  1456.         break;
  1457.       }
  1458.     free( outPathname );
  1459.     }
  1460.       }
  1461.       /* Else there was no chunk found for this file, or we have already made an
  1462.        * attempt to open it for output, and it has failed.
  1463.        */
  1464.       fileTree->status |= xrecover_started;
  1465.       /* Either we started it this run, or it was already started, so this makes
  1466.        * no difference
  1467.        */
  1468.     }
  1469.  
  1470.     else ++problemFiles;
  1471.     fileTree = fileTree->next;
  1472.   }
  1473.  
  1474.   return problemFiles;
  1475. }
  1476.  
  1477. static int xrecover_RecoverTree( FILE *in, const char *victim,
  1478.                  const char *outNameBuffer,
  1479.                  char *victimGoesHere,
  1480.                  unsigned chunkCount,
  1481.                  const xFiles_header *header,
  1482.                  const xFiles_chunk *chunkTable,
  1483.                  const unsigned long *rootDirOffset,
  1484.                  const unsigned maxChunk, BOOL dirsAsText,
  1485.                  BOOL suppressFiles, const BOOL useAlloc,
  1486.                  const int verbose )
  1487. {
  1488.   unsigned rootDirChunk = header ? header->rootChunk : 0;
  1489.   unsigned long useRootDirOffset;
  1490.   xrecover_chunkTableCrossCheck *crossCheckTable;
  1491.   xrecover_filenameListEntry *fileTree = 0;
  1492.   unsigned loop;
  1493.   unsigned problemFiles, unclaimedChunks = 0;
  1494.  
  1495.   if( rootDirOffset == 0 )
  1496.   {
  1497.     if( rootDirChunk == 0 || chunkTable == 0 )
  1498.     {
  1499.       fputs( "Can't find root directory.\n", stderr );
  1500.       return 1;
  1501.     }
  1502.     useRootDirOffset = ( chunkTable + rootDirChunk )->offset;
  1503.   }
  1504.   else
  1505.   {
  1506.     useRootDirOffset = *rootDirOffset;
  1507.     rootDirChunk = xrecover_chunkFromOffset( chunkTable, maxChunk,
  1508.                          useRootDirOffset );
  1509.     if( verbose > 1 )
  1510.     {
  1511.       printf( "Root dir offset %ld ", useRootDirOffset );
  1512.       if( rootDirChunk )
  1513.       {
  1514.     printf( "corresponds to chunk %d\n", rootDirChunk );
  1515.       }
  1516.       else
  1517.       {
  1518.     puts( "does not correspond to any used chunk" );
  1519.       }
  1520.     }
  1521.   }
  1522.  
  1523.   if( fseek( in, useRootDirOffset, SEEK_SET ) )
  1524.   {
  1525.     fprintf( stderr, "Could not seek to root directory at %ld\n",
  1526.          useRootDirOffset );
  1527.     return 1;
  1528.   }
  1529.   else if( verbose > 1 )
  1530.   {
  1531.     printf( "Using root directory at %ld\n", useRootDirOffset );
  1532.   }
  1533.  
  1534.   if( 0 == ( crossCheckTable = malloc( sizeof( xrecover_chunkTableCrossCheck )
  1535.                        * maxChunk ) ) )
  1536.   {
  1537.     fprintf( stderr, "Failed to get %d bytes of memory for crossCheckTable\n",
  1538.          sizeof( xrecover_chunkTableCrossCheck ) * maxChunk );
  1539.     return 1;
  1540.   }
  1541.  
  1542.   for( loop = maxChunk; loop-- != 0; )
  1543.   {
  1544.     crossCheckTable[loop].file = NULL;
  1545.     crossCheckTable[loop].status = xrecover_untouched;
  1546.  
  1547.   }
  1548.  
  1549.   xrecover_recoverDir( in, 0, victim, "", useRootDirOffset, rootDirChunk, 0,
  1550.                chunkTable, crossCheckTable, &fileTree, maxChunk,
  1551.                verbose );
  1552.  
  1553.   /*
  1554.   xrecover_printList( stdout, chunkTable, crossCheckTable, fileTree, verbose );       */
  1555.  
  1556.   problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
  1557.                     chunkTable, crossCheckTable,
  1558.                     fileTree, useAlloc, verbose );
  1559.  
  1560.  
  1561.   for( loop = maxChunk; --loop > 0; )
  1562.   /* 1 rather than 0 so that we miss the chunkTable chunk */
  1563.   {
  1564.     if( chunkTable[loop].usage != FREE && loop != rootDirChunk )
  1565.     {
  1566.       if( crossCheckTable[loop].file == NULL )
  1567.       {
  1568.     unclaimedChunks++;
  1569.       }
  1570.       else
  1571.       {
  1572.     test( crossCheckTable[loop].references == 1,
  1573.           "Chunk %d has %d references\n", loop,
  1574.           crossCheckTable[loop].references );
  1575.       }
  1576.     }
  1577.   }
  1578.  
  1579.   if( verbose > 0 )
  1580.   {
  1581.     if( problemFiles == 1 )
  1582.     {
  1583.       fputs( "There is one problem file", stdout );
  1584.     }
  1585.     else
  1586.     {
  1587.       printf( "There are %d problem files", problemFiles );
  1588.     }
  1589.     printf( " and %d unclaimed chunk%s\n", unclaimedChunks,
  1590.         ( unclaimedChunks == 1 ) ? "" : "s" );
  1591.   }
  1592.  
  1593.  
  1594.   if( problemFiles || unclaimedChunks )
  1595.   {
  1596.     /* Here goes. Let's try to match them up */
  1597.     xrecover_filenameListEntry **problemFile;
  1598.     const xFiles_chunk **unclaimedChunk, **currentChunk;
  1599.  
  1600.     problemFile = malloc( sizeof( xrecover_filenameListEntry *)
  1601.               * problemFiles );
  1602.     unclaimedChunk = malloc( sizeof( xFiles_chunk ** ) * unclaimedChunks );
  1603.  
  1604.     if( problemFile == 0 && unclaimedChunk == 0 )
  1605.     {
  1606.       fputs( "Could not get memory to sort problem files and unclaimed "
  1607.          "chunks\n", stderr );
  1608.     }
  1609.     else
  1610.     {
  1611.       unsigned int lastSize = UINT_MAX;
  1612.       currentChunk = unclaimedChunk;
  1613.       /* Entry can never (reasonably) be this big as there will be some bytes
  1614.        * used for the (header/chunkTable/dir) */
  1615.       for( loop = maxChunk; --loop > 0 ; )
  1616.       {
  1617.     if( chunkTable[loop].usage != FREE && loop != rootDirChunk
  1618.         && crossCheckTable[loop].file == NULL )
  1619.     {
  1620.       *currentChunk++ = &(chunkTable[loop]);
  1621.     }
  1622.       }
  1623.  
  1624.       qsort( (void *) unclaimedChunk, unclaimedChunks, sizeof( xFiles_chunk * ),
  1625.          chunkSizeCmp );
  1626.  
  1627.       xrecover_getProblemList( fileTree, problemFile, problemFiles );
  1628.  
  1629.       qsort( problemFile, problemFiles, sizeof( xrecover_filenameListEntry * ),
  1630.          filenameListEntryCmp );
  1631.  
  1632.       test( currentChunk == unclaimedChunk + unclaimedChunks,
  1633.         "Awooga - can't count - first time there were %d unclaimed chunks, "
  1634.         "now there are %d\n", unclaimedChunks,
  1635.         currentChunk - unclaimedChunk );
  1636.  
  1637.       for( loop = problemFiles; loop-- > 0 ; )
  1638.       {
  1639.     const char *thisFile = ( (char *) &(problemFile[loop]->dirEntry) )
  1640.                    + sizeof( xFiles_dirEntry );
  1641.     const unsigned thisSize = problemFile[loop]->dirEntry.size;
  1642.     unsigned index;
  1643.  
  1644.     /* If this file has the same size as the previous file ignore it */
  1645.     if( thisSize == lastSize ) continue;
  1646.     /* If this file has the same size as the next file,
  1647.        0: Make sure that the next file is ignored too
  1648.        1: Ignore it
  1649.      */
  1650.     if( loop > 0 && ( problemFile[loop-1]->dirEntry.size == thisSize ) )
  1651.     {
  1652.       lastSize = thisSize;
  1653.       if( verbose > 0 )
  1654.       {
  1655.         printf( "As files '%s' and '%s' have same size (%d) "
  1656.             "cannot attempt to match to unclaimed chunks\n", thisFile,
  1657.             ( (char *) &(problemFile[loop - 1]->dirEntry) )
  1658.               + sizeof( xFiles_dirEntry ), thisSize );
  1659.       }
  1660.       continue;
  1661.     }
  1662.  
  1663.  
  1664.     while( ( currentChunk-- > unclaimedChunk )
  1665.            && chunkTable[ index = *currentChunk - chunkTable ].size
  1666.           > thisSize );
  1667.     /* 8-). index = *currentChunk - chunkTable; somewhere in the middle of
  1668.      * all that. Anyway, we get here when a chunk size matches or we run out
  1669.      * of chunks
  1670.      */
  1671.  
  1672.     if( currentChunk < unclaimedChunk    /* Out of chunks */
  1673.         || chunkTable[index].size < thisSize )
  1674.     /* index won't be used here before it is set */
  1675.     {
  1676.       if( verbose > 0 )
  1677.       {
  1678.         printf( "Cannot find chunk to match file '%s' size %d\n", thisFile,
  1679.             thisSize );
  1680.       }
  1681.     }
  1682.     else if( currentChunk > unclaimedChunk    /* At least one more chunk */
  1683.          && chunkTable[ *( currentChunk - 1 ) - chunkTable ].size
  1684.             == thisSize )
  1685.     {
  1686.       if( verbose > 0 )
  1687.       {
  1688.         printf( "More than one chunk to match file '%s' size %d\n",
  1689.             thisFile, thisSize );
  1690.       }
  1691.       /* Skip past chunks of this size */
  1692.       while( ( currentChunk-- > unclaimedChunk )
  1693.            && chunkTable[ *currentChunk - chunkTable ].size == thisSize );
  1694.     }
  1695.     else
  1696.     {
  1697.       /* ¡!¡Hit!¡!
  1698.        * Exactly one file of this size matched exactly one chunk.
  1699.        * Let's brag about it on stderr
  1700.        */
  1701.       fprintf( stderr, "Chunk %d size %d matches file '%s' size %d\n"
  1702.            "Will attempt recovery of this chunk as this file\n",
  1703.            index, chunkTable[index].size, thisFile, thisSize );
  1704.       /* Reset the flags so that a new attempt on recovery will be made */
  1705.       problemFile[loop]->status |= xrecover_foundChunk
  1706.                        | xrecover_sizeMatches;
  1707.       problemFile[loop]->status &= ~xrecover_started;
  1708.       problemFile[loop]->node = index;
  1709.       crossCheckTable[index].references = 1;
  1710.       crossCheckTable[index].file = problemFile[loop];
  1711.     }
  1712.       }
  1713.  
  1714.  
  1715.  
  1716.     }
  1717.  
  1718.     free( problemFile );
  1719.     free( (void *) unclaimedChunk );
  1720.  
  1721.     /* Let's have another go */
  1722.     problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
  1723.                       chunkTable, crossCheckTable,
  1724.                       fileTree, useAlloc, verbose );
  1725.   }
  1726.  
  1727.   xrecover_freeList( &fileTree );
  1728.   /* Don't need this anymore. Now go for the unclaimed chunks */
  1729.  
  1730.   if( victim )
  1731.   {
  1732.     for( loop = maxChunk; --loop > 0 ; )
  1733.     {
  1734.       if( chunkTable[loop].usage == FREE || loop == rootDirChunk
  1735.       || crossCheckTable[loop].file != 0 )
  1736.     continue;
  1737.  
  1738.       /* Unreferenced, non-free chunk. Looks ripe for recovery */
  1739.       if( victim )
  1740.       {
  1741.     sprintf( victimGoesHere, chkdskString, chunkCount++ );
  1742.       }
  1743.       /* outNameBuffer == 0 iff victim == 0 */
  1744.       xrecover_recoverChunkChkdsk( in, outNameBuffer, loop,
  1745.                    chunkTable + loop,
  1746.                    useAlloc ? chunkTable[loop].allocSize
  1747.                         : 0,
  1748.                    dirsAsText, suppressFiles, verbose );
  1749.     }
  1750.   }
  1751.  
  1752.   free( crossCheckTable );
  1753.   return 0;
  1754. }
  1755.  
  1756. static int xrecover_RecoverFile( const char *problem, const char *victim,
  1757.                  const unsigned long *chunkTableOffset,
  1758.                  const unsigned long *rootDirOffset,
  1759.                  const xrecover_qualityLevel qualityOfJob,
  1760.                  const BOOL dirsAsText,
  1761.                  const BOOL canCreateVictim,
  1762.                  const BOOL suppressFiles, const BOOL writeFree,
  1763.                  const BOOL useAlloc, const int verbose )
  1764. {
  1765.   /* Victim == 0 to suppress all writes */
  1766.   FILE *in;
  1767.   long problemSize;
  1768.   size_t victimPathLen = victim ? strlen( victim ) + strlen( xrecover_dirSep )
  1769.                 : 0;
  1770.   size_t outNameBufferLen = victim ? ( victimPathLen
  1771.                        + strlen( chkdskString ) + 1  )
  1772.                    : 0;
  1773.   char *outNameBuffer = victim ? malloc( outNameBufferLen ) : 0;
  1774.   unsigned maxChunk;
  1775.   unsigned readChunks;
  1776.   unsigned chunkLoop;
  1777.   unsigned chunkCount = 0;
  1778.   unsigned long seekTo;
  1779.   xFiles_header header;
  1780.   xFiles_chunk *chunkTable;
  1781.   xFiles_chunk rootChunk;
  1782.   const xFiles_chunk **sortedChunkTable, **currentChunk, **endSorted;
  1783.   unsigned long endLastChunk = 0;
  1784. #ifdef __riscos
  1785.   _kernel_swi_regs regs;
  1786.   _kernel_oserror *err;
  1787.   int type;
  1788. #endif
  1789.  
  1790.  
  1791. #ifdef __riscos
  1792.   if( victim )
  1793.   {
  1794.     regs.r[0] = 5;
  1795.     regs.r[1] = (int) victim;
  1796.  
  1797.  
  1798.     if( err = _kernel_swi( OS_File, ®s, ®s ), err )
  1799.     {
  1800.       fprintf( stderr, "%s\n", err->errmess );
  1801.       return 1;
  1802.     }
  1803.  
  1804.     type = regs.r[0];
  1805.  
  1806.     if( type == 0 && canCreateVictim )
  1807.     {
  1808.       if( verbose > 0 ) printf( "Creating x-file '%s'\n", victim );
  1809.       regs.r[0] = 11;
  1810.       regs.r[2] = xFiles_TYPE;
  1811.       regs.r[4] = 0;
  1812.       regs.r[5] = 0;
  1813.       if( err = _kernel_swi( OS_File, ®s, ®s ), err )
  1814.       {
  1815.     fprintf( stderr, "%s\n", err->errmess );
  1816.     return 1;
  1817.       }
  1818.     }
  1819.     else if( 0 == ( type & 2 ) )
  1820.     {
  1821.       /* Use OS File to make a message */
  1822.       regs.r[0] = 19;
  1823.       regs.r[2] = type;
  1824.       err = _kernel_swi( OS_File, ®s, ®s );
  1825.       if( !err ) err = (_kernel_oserror *) regs.r[0];
  1826.       /* Get the generated error message if no erroneous error occurred  */
  1827.       fprintf( stderr, "%s\n", err->errmess );
  1828.       return 1;
  1829.     }
  1830.   }
  1831. #endif    /* end of Risc OS specific bits */
  1832.  
  1833.   /* hopefully from now on is ANSI */
  1834.  
  1835.   if( victim )
  1836.   {
  1837.     if( 0 == outNameBuffer )
  1838.     {
  1839.       fprintf( stderr, "Failed to get %d bytes of memory for outNameBuffer\n",
  1840.          outNameBufferLen );
  1841.       return 1;
  1842.     }
  1843.     sprintf( outNameBuffer, "%s%s", victim, xrecover_dirSep );
  1844.   }
  1845.  
  1846.   if( 0 == ( in = fopen( problem, "rb" ) ) )
  1847.   {
  1848.     fprintf( stderr, "Could not open file '%s'\n", problem );
  1849.     free( outNameBuffer );
  1850.     return 1;
  1851.   }
  1852.  
  1853.   if( fseek( in, 0, SEEK_END ) || ( -1L == ( problemSize = ftell( in ) ) ) )
  1854.   {
  1855.     fprintf( stderr, "Could not find size of file '%s'\n", problem );
  1856.     free( outNameBuffer );
  1857.     return 1;
  1858.   }
  1859.  
  1860.   rewind( in );
  1861.  
  1862.   if( 0 == fread( &header, sizeof( header ), 1, in ) )
  1863.   {
  1864.     fprintf( stderr, "Could not read header from file '%s'\n", problem );
  1865.     free( outNameBuffer );
  1866.     return 1;
  1867.   }
  1868.  
  1869.   test( header.sig == xFiles_SIG,
  1870.     "Missing xFiles_SIG - read %8X, should be %8X\n",
  1871.     header.sig, xFiles_SIG );
  1872.   if( verbose > 1 )
  1873.   {
  1874.     test( header.hdrSize == sizeof( xFiles_header ),
  1875.       "Illegal hdrSize %d\n",
  1876.       header.hdrSize );
  1877.  
  1878.     test( header.structureVersion == xFiles_STRUCTUREVERSION,
  1879.       "Illegal structureVersion %d\n",
  1880.         header.structureVersion );
  1881.  
  1882.     test( header.directoryVersion == xFiles_DIRECTORYVERSION,
  1883.     "Illegal directoryVersion %d\n",
  1884.       header.directoryVersion );
  1885.  
  1886.   /* testChunk(&info, 0, &  header.chunkTable ); */
  1887.  
  1888.   test( power2( header.allocationUnit ),
  1889.     "Illegal allocationUnit %d\n", header.allocationUnit );
  1890.   }
  1891.  
  1892.   /*
  1893.   test( ( roundUp( &header, sizeof( header ) ) == header.chunkTable.offset ),
  1894.     "chunkTable not found 1 allocationUnit into file\n"
  1895.     "Expected %d\n"
  1896.     "Actually %d\n", roundUp( &header, sizeof( header ) ),
  1897.     header.chunkTable.offset ); */
  1898.  
  1899.   seekTo = chunkTableOffset ? *chunkTableOffset : header.chunkTable.offset;
  1900.   if( fseek( in, seekTo, SEEK_SET ) )
  1901.   {
  1902.     fprintf( stderr, "Could not seek to chunkTable at %ld\n", seekTo );
  1903.     free( outNameBuffer );
  1904.     return 1;
  1905.   }
  1906.  
  1907.    ;
  1908.  
  1909.   if( 0 == fread( &rootChunk, sizeof( xFiles_chunk ), 1, in ) )
  1910.   {
  1911.     fprintf( stderr,
  1912.          "Could not read first chunk of chunkTable from file '%s'\n",
  1913.          problem );
  1914.     free( outNameBuffer );
  1915.     return 1;
  1916.   }
  1917.  
  1918.   /* Derive size from header, unless chunkTable offset was specified */
  1919.   maxChunk = ( chunkTableOffset ? rootChunk.size
  1920.                   : header.chunkTable.size )
  1921.                    / sizeof( xFiles_chunk );
  1922.  
  1923.   test( 0 == ( header.chunkTable.size % sizeof( xFiles_chunk ) ),
  1924.     "chunkTable size %d (from header) is not a multiple of %d\n",
  1925.     header.chunkTable.size, sizeof( xFiles_chunk ) );
  1926.  
  1927.   test( 0 == ( rootChunk.size % sizeof( xFiles_chunk ) ),
  1928.     "chunkTable size %d (from chunkTable) is not a multiple of %d\n",
  1929.     rootChunk.size, sizeof( xFiles_chunk ) );
  1930.  
  1931.   if( 0 == ( chunkTable = malloc( header.chunkTable.size ) ) )
  1932.   {
  1933.     fprintf( stderr, "Failed to get %d bytes of memory for chunkTable\n",
  1934.          header.chunkTable.size );
  1935.     fclose( in );
  1936.     free( outNameBuffer );
  1937.     return 1;
  1938.   }
  1939.   if( 0 == ( sortedChunkTable = malloc( sizeof( xFiles_chunk ** ) * maxChunk ) )    )
  1940.   {
  1941.     fprintf( stderr, "Failed to get %d bytes of memory to sort chunkTable\n",
  1942.          sizeof( xFiles_chunk ** ) * maxChunk );
  1943.     free( chunkTable );
  1944.     free( outNameBuffer );
  1945.     fclose( in );
  1946.     return 1;
  1947.   }
  1948.  
  1949.   if( verbose > 0 )
  1950.   {
  1951.     printf( "'%s' has %d chunk(s)\n", problem, maxChunk );
  1952.   }
  1953.  
  1954.   *chunkTable = rootChunk;
  1955.   
  1956.   readChunks = fread( chunkTable + 1, sizeof( xFiles_chunk ), maxChunk - 1,
  1957.                 in );
  1958.  
  1959.   if( readChunks < maxChunk - 1 )
  1960.   {
  1961.     fprintf( stderr,
  1962.          "Only read %d chunks out of %d allegedly in chunkTable\n"
  1963.          "Will only deal with these %d chunkss\n",
  1964.          readChunks + 1, maxChunk, readChunks + 1);
  1965.     maxChunk = readChunks + 1;
  1966.   }
  1967.  
  1968.  
  1969.   for( chunkLoop = maxChunk ; chunkLoop-- > 0;
  1970.        sortedChunkTable[chunkLoop] = &(chunkTable[chunkLoop]) );
  1971.  
  1972.   qsort( (void *) sortedChunkTable, maxChunk, sizeof( xFiles_chunk * ),
  1973.      chunkOffsetCmp );
  1974.  
  1975.   if( verbose > 1 )
  1976.   {
  1977.     puts( "Chunk  Offset    Size   Usage Allocated" );
  1978.     /* Puts adds \n. Isn't the ANSI C library wonderfully consistent:
  1979.      * puts( a ) == fputs( a + '\n', stdout ) == fprintf( stdout, "%s\n", a ) */
  1980.   }
  1981.  
  1982.   /* OK. Go through these in offset order */
  1983.   for( currentChunk = sortedChunkTable, endSorted = sortedChunkTable + maxChunk;
  1984.        currentChunk < endSorted; currentChunk++ )
  1985.   {
  1986.     chunkLoop = *currentChunk - chunkTable;
  1987.  
  1988.     if( chunkTable[chunkLoop].usage == FREE )
  1989.     {
  1990.       if( verbose > 4 )
  1991.       {
  1992.     printf( " %4d %7d            FREE\n", chunkLoop,
  1993.         chunkTable[chunkLoop].offset );
  1994.       }
  1995.       test( chunkTable[chunkLoop].offset < maxChunk,
  1996.         "Chunk %d (free) bad offset %08x\n", chunkLoop,
  1997.         chunkTable[chunkLoop].offset );
  1998.  
  1999.       test( chunkTable[chunkLoop].size == FREE,
  2000.         "Chunk %d (free) size is %08x (should be %08x)\n",
  2001.         chunkLoop, chunkTable[chunkLoop].size, FREE );
  2002.  
  2003.       test( chunkTable[chunkLoop].allocSize == FREE,
  2004.         "Chunk %d (free) allocSize is %08x (should be %08x)\n",
  2005.         chunkLoop, chunkTable[chunkLoop].allocSize, FREE );
  2006.     }
  2007.     else
  2008.     {
  2009.       test( okOffset( &header, chunkTable[chunkLoop].offset ),
  2010.         "Chunk %d (used) bad offset %08x\n", chunkLoop,
  2011.         chunkTable[chunkLoop].offset );
  2012.  
  2013.       if( endLastChunk != -1L )
  2014.       {
  2015.     /* offset is unsigned - if it's 0 for start of file then offset can
  2016.        *never* be less than endLastChunk */
  2017.     if( chunkTable[chunkLoop].offset < endLastChunk )
  2018.     {
  2019.       fprintf( stderr, "Chunk %d offset %d overlaps end of previous chunk"
  2020.            " (%d, ends at %ld)\n", chunkLoop,
  2021.            chunkTable[chunkLoop].offset,
  2022.            *( currentChunk - 1 ) - chunkTable, endLastChunk );
  2023.     }
  2024.     else if( chunkTable[chunkLoop].offset > endLastChunk )
  2025.     {
  2026.       if( verbose > 1 )
  2027.       {
  2028.         printf( "%ld byte(s) %s between ",
  2029.         chunkTable[chunkLoop].offset - endLastChunk,
  2030.         endLastChunk ? "free space" : "x-file header" );
  2031.  
  2032.         if( endLastChunk )
  2033.         {
  2034.           printf( "chunk %d", *( currentChunk - 1 ) - chunkTable );
  2035.         }
  2036.         else
  2037.         {
  2038.           fputs( "start of file", stdout );    /* puts appends '\n' */
  2039.         }
  2040.  
  2041.         printf( " and chunk %d\n", chunkLoop );
  2042.  
  2043.       }
  2044.       if( writeFree && victim )
  2045.       {
  2046.         sprintf( outNameBuffer + victimPathLen, chkdskString,
  2047.              chunkCount++ );
  2048.         xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
  2049.                       chunkTable[chunkLoop].offset
  2050.                       - endLastChunk, verbose );
  2051.         }
  2052.     }
  2053.  
  2054.       }
  2055.  
  2056.       if( verbose > 1 )
  2057.       {
  2058.     printf( " %4d %7.d %7.d %7.d %9.d\n", chunkLoop,
  2059.         chunkTable[chunkLoop].offset, chunkTable[chunkLoop].size,
  2060.         chunkTable[chunkLoop].usage, chunkTable[chunkLoop].allocSize );
  2061.       }
  2062.       test( (chunkTable[chunkLoop].size <= chunkTable[chunkLoop].allocSize ),
  2063.         "Chunk %d (used) bad size %08x (allocSize %08x)\n",
  2064.         chunkLoop, chunkTable[chunkLoop].size,
  2065.         chunkTable[chunkLoop].allocSize );
  2066.       test( okOffset( &header, chunkTable[chunkLoop].allocSize ),
  2067.         "Chunk %d (used) bad allocSize %08x\n", chunkLoop,
  2068.         chunkTable[chunkLoop].allocSize );
  2069.       test( ( problemSize > chunkTable[chunkLoop].offset ),
  2070.         "Chunk %d (used) offset %08x > size of '%s' (%08x)\n", chunkLoop,
  2071.         chunkTable[chunkLoop].offset, problem, problemSize );
  2072.  
  2073.       endLastChunk = chunkTable[chunkLoop].offset
  2074.              + chunkTable[chunkLoop].allocSize;
  2075.  
  2076.       /* Whatever the method, recover the chunktable here if useAlloc is set */
  2077.       if( qualityOfJob == xrecover_qualityChkdsk
  2078.       || ( chunkLoop == 0 && useAlloc ) )
  2079.       {
  2080.     if( victim )
  2081.     {
  2082.       sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
  2083.     }
  2084.     /* outNameBuffer == 0 iff victim == 0 */
  2085.     xrecover_recoverChunkChkdsk( in, outNameBuffer, chunkLoop,
  2086.                      chunkTable + chunkLoop,
  2087.                      useAlloc ? chunkTable[chunkLoop].allocSize
  2088.                           : 0,
  2089.                      dirsAsText, suppressFiles, verbose );
  2090.       }
  2091.     }
  2092.   }
  2093.  
  2094.   if( endLastChunk != -1L && problemSize > endLastChunk )
  2095.   {
  2096.     /* offset is unsigned - if it's 0 for start of file then offset can
  2097.        *never* be less than endLastChunk */
  2098.     if( verbose > 1 )
  2099.     {
  2100.       printf( "%ld byte(s) free space between chunk %d and end of file\n",
  2101.           problemSize - endLastChunk, *( currentChunk - 1 ) - chunkTable );
  2102.     }
  2103.  
  2104.     if( writeFree && victim )
  2105.     {
  2106.       sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
  2107.       xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
  2108.                     problemSize - endLastChunk, verbose );
  2109.     }
  2110.   }
  2111.  
  2112.   switch( qualityOfJob )
  2113.   {
  2114.     case xrecover_qualityChkdsk:
  2115.     break;    /* Done this while checking chunk table */
  2116.  
  2117.     case xrecover_qualityReadDirs:
  2118.     {
  2119.       xrecover_RecoverTree( in, victim, outNameBuffer,
  2120.                 outNameBuffer + victimPathLen, chunkCount,
  2121.                 &header, chunkTable, rootDirOffset,
  2122.                 maxChunk, dirsAsText, suppressFiles, useAlloc,
  2123.                 verbose );
  2124.     }
  2125.     break;
  2126.  
  2127.     default:
  2128.     fprintf( stderr, "Unimplemented quality %d\n", qualityOfJob );
  2129.   }
  2130.   free( sortedChunkTable );
  2131.   free( chunkTable );
  2132.   free( outNameBuffer );
  2133.   /* Some systems will memory leak if one doesn't do this */
  2134.   /* (Not freeing things is a Vaxism (and Unixism) ) */
  2135.   return 0;
  2136. }
  2137.  
  2138. int main( int argc, char *argv[] )
  2139. {
  2140.   int argn;
  2141.   int verbose = 0;
  2142.   const char *problem = 0;    /* aka x-file to fix */
  2143.   const char  *victim = 0;    /* aka destination 'directory' */
  2144.   BOOL dirsAsText = TRUE;
  2145.   BOOL suppressOutput = FALSE;
  2146.   BOOL canCreateVictim = FALSE;
  2147.   BOOL suppressFiles = FALSE;
  2148.   BOOL xguess = FALSE;
  2149.   BOOL useAlloc = FALSE;
  2150.   BOOL writeFree = FALSE;
  2151.   xrecover_Offset rootDir = { FALSE, 0 };
  2152.   xrecover_Offset chunkTable = { FALSE, 0 };
  2153.   xrecover_qualityLevel qualityOfJob = xrecover_qualityReadDirs;
  2154.  
  2155. #ifdef __riscos
  2156. #ifdef __GNUC__
  2157.   __uname_control |= __UNAME_NO_PROCESS;
  2158. #endif
  2159. #endif
  2160.   for( argn = 1; ( argn < argc ) && ( *argv[argn] == '-' ); argn++ )
  2161.   {
  2162.     const char *current = argv[argn] + 1;
  2163.     if( *current == '-' )
  2164.     {
  2165.       argn++;
  2166.       break;    /* Closet goto out of the for loop */
  2167.       /* -- marks end of options à la Unix */
  2168.     }
  2169.     while( current && *current )
  2170.     {
  2171.       switch( tolower( *current ) )
  2172.       {
  2173.     case 'a':
  2174.       useAlloc = TRUE;
  2175.       break;
  2176. #ifdef __riscos
  2177.     case 'c':
  2178.       canCreateVictim = TRUE;
  2179.       break;
  2180. #endif
  2181.     case 'd':
  2182.       dirsAsText = FALSE;
  2183.       break;
  2184.     case 'f':
  2185.       writeFree = TRUE;
  2186.       break;
  2187.     case 'g':
  2188.       xguess = TRUE;
  2189.     case 'n':
  2190.       suppressOutput = TRUE;
  2191.       break;
  2192.     case 's':
  2193.       suppressFiles = TRUE;
  2194.       break;
  2195.     case 'v':
  2196.       verbose += 1;
  2197.       break;
  2198.  
  2199.     case 'r':
  2200.       if( *(current + 1) )
  2201.       {
  2202.         if( FALSE == xrecover_ReadOffset( &rootDir, current + 1 ) )
  2203.         {
  2204.           argn = argc + 1;
  2205.         }
  2206.       }
  2207.       else
  2208.       {
  2209.         argn = xrecover_ReadOffset( &rootDir, argv[argn+1] ) ? argn + 1
  2210.                                  : argc + 1;
  2211.       }
  2212.       current = 0; current--;
  2213.       /* Whatever happens, skip on to next argv */
  2214.       break;
  2215.     /* If TRUE, value is successfully read, so skip over value
  2216.      * Else argn = argc will break out of this loop
  2217.      * AND trigger syntax message */
  2218.     case 't':
  2219.       if( *(current + 1) )
  2220.       {
  2221.         if( FALSE == xrecover_ReadOffset( &chunkTable, current + 1 ) )
  2222.         {
  2223.           argn = argc + 1;
  2224.         }
  2225.       }
  2226.       else
  2227.       {
  2228.         argn = xrecover_ReadOffset( &chunkTable, argv[argn+1] ) ? argn + 1
  2229.                                     : argc + 1;
  2230.       }
  2231.       current = 0; current--;
  2232.       /* Whatever happens, skip on to next argv */
  2233.       break;
  2234.  
  2235.     case '0':
  2236.       qualityOfJob= xrecover_qualityEvery1024;
  2237.       break;
  2238.     case '1':
  2239.       qualityOfJob= xrecover_qualityChkdsk;
  2240.       break;
  2241.     case '2':
  2242.       qualityOfJob= xrecover_qualityReadDirs;
  2243.       break;
  2244.  
  2245.     case 'm':
  2246.       if( *(current + 1) == '$' )
  2247.       {
  2248.         chkdskString = chkdskStringMS;
  2249.         current++;
  2250.         break;
  2251.       } /* Else fall through to this: */
  2252.     default:
  2253.       fprintf( stderr, "Ignoring unknown option '%c'\n", *current );
  2254.       break;
  2255.       }
  2256.       current++;
  2257.     }    /* while( current && *current ) */
  2258.   }
  2259.  
  2260.   if( argn < argc )
  2261.   {
  2262.     problem = argv[argn++];
  2263.   }
  2264.   if( argn < argc )
  2265.   {
  2266.     victim = argv[argn++];
  2267.   }
  2268.  
  2269.   if( ( ( victim == 0 ) && !suppressOutput ) || ( argn != argc ) )
  2270.   {
  2271.     /* When is Acorn's F***ing Run time going to do tabs properly? */
  2272.     /* Use gcc - free software always offers better value for money */
  2273.     fputs( "Syntax:\tx-recover [options] <x-file> <dest-dir>\n"
  2274.        "\t\t-v for verbose info (more v's for more verbosity!)\n"
  2275. #ifdef __riscos
  2276.        "\t\t-c to create dest-dir as an x-file if necessary\n"
  2277. #endif
  2278.        "\t\t-d To output directories in raw binary form\n"
  2279.        "\t\t-g To scan file to find the chunk table and root directory\n"
  2280.        "\t\t   (implies -n)\n"
  2281.        "\t\t-n To suppress all disc output (still report integrity checks)"
  2282.        "\n"
  2283.        "\t\t-r <offset> to specify the offset of the root directory\n"
  2284.        "\t\t-s To suppress all file output (but create directory tree)\n"
  2285.        "\t\t-t <offset> to specify the offset of the chunkTable\n"
  2286.        "\t\t-a To round up all file sizes to the size of the chunk\n"
  2287.        "\t\t-f To write out all free space between chunks as files\n"
  2288. /*       "\t\t-e To round up the x-file to the size allocated on disc\n" */
  2289. /* nasty filing system fills it with zeros, so not much point. I don't think
  2290.  * ADFS did this on Risc OS 2 (Network filing systems did, being security
  2291.  * concious */
  2292. /*       "\t\t-0 Attempt to split chunks ignoring chunk table\n" */
  2293.        "\t\t-1 Output all used chunks as files (needs intact chunk table)\n"
  2294.        "\t\t-2 Attempt to traverse the directory tree and recreate the \n"
  2295.        "\t\t   x-file (default method)\n",
  2296.        stderr );
  2297.     return 1;
  2298.   }
  2299.  
  2300.   if( xguess ) return xrecover_xguess( problem );
  2301.  
  2302.   if( verbose > 0 )
  2303.   {
  2304.     if( victim )
  2305.     {
  2306.       printf( "%s -> %s (v=%d)\n", problem, victim, verbose );
  2307.     }
  2308.     else
  2309.     {
  2310.       printf( "%s [suppress output] (v=%d)\n", problem, verbose );
  2311.     }
  2312.     if( rootDir.valid )
  2313.     {
  2314.       printf( "Read root directory from file offset %ld\n", rootDir.offset );
  2315.     }
  2316.     if( chunkTable.valid )
  2317.     {
  2318.       printf( "Read chunk table from file offset %ld\n", chunkTable.offset );
  2319.     }
  2320.   }
  2321.   return xrecover_RecoverFile( problem, suppressOutput ? 0 : victim,
  2322.                    chunkTable.valid ? &(chunkTable.offset) : 0,
  2323.                    rootDir.valid ? &(rootDir.offset) : 0,
  2324.                    qualityOfJob, dirsAsText,
  2325.                    canCreateVictim, suppressFiles, writeFree,
  2326.                    useAlloc, verbose );
  2327. }
  2328.  
  2329.  
  2330.